%%%-------------------------------------------------------------------
%%% @author Rain
%%% @doc 生成proto文件
%%% @end
%%%-------------------------------------------------------------------
-module(build_proto).
-author("Rain").
-include("build_proto.hrl").

%% API
-export([build/3]).
-export([erase_all_proc_dict/0]).


%% @doc 根据构建数据构建proto文件
build(#build_proto{proto_name = ProtoName,
    cmd_main_id = CmdMainId,
    imports = Imports,
    structs = Structs,
    messages = Messages}, ProtoOutPath, ProtoVersion) ->
    io:format("Build Proto ~s(~p) Start ...~n", [ProtoName, CmdMainId]),
    ProtoFile = filename:join(ProtoOutPath, ProtoName ++ ".proto"),
    {ok, ProtoFileDevice} = file:open(ProtoFile, [write]),
    {ok, MP} = re:compile("^\\s*(.*?)\\s*//\\s*(.*?)\\s*$", [unicode]),
    set_msg_desc_mp(MP),
    set_proto_file_device(ProtoFileDevice),
    io:format(ProtoFileDevice, "~s~s", [base_header(), proto_header(ProtoVersion)]),
    build_proto_import(Imports),
    build_proto_struct(Structs, ProtoVersion),
    build_proto_message(Messages, ProtoVersion, ProtoName, CmdMainId),
    file:close(ProtoFileDevice),
    io:format("Build Proto ~s(~p) Done !!!~n", [ProtoName, CmdMainId]).

set_msg_desc_mp(MP) ->
    put(msg_desc_mp, MP).

get_msg_desc_mp() ->
    get(msg_desc_mp).

set_proto_file_device(ProtoFileDevice) ->
    put(proto_file_device, ProtoFileDevice).

get_proto_file_device() ->
    get(proto_file_device).


set_msg_main_maps(Maps) ->
    put(msg_main_maps, Maps).

get_msg_main_maps() ->
    case get(msg_main_maps) of
        undefined ->
            #{};
        Any ->
            Any
    end.

erase_all_proc_dict() ->
    erase(msg_desc_mp),
    erase(proto_file_device),
    erase(msg_main_maps).


base_header() ->
    "// Automatically generated, do not edit\n\n".

proto_header(?PROTO_VERSION_2) ->
    "syntax = \"proto2\";\npackage Game;\n\n";
proto_header(?PROTO_VERSION_3) ->
    "syntax = \"proto3\";\npackage Game;\n\n".

%% @doc 构建导入协议
build_proto_import([]) ->
    ok;
build_proto_import([ImportFile | T]) ->
    Device = get_proto_file_device(),
    io:format(Device, "import \"~s\" ;~n", [ImportFile]),
    build_proto_import(T).

%% @doc 构建前后端交互消息
build_proto_message([], _ProtoVersion, _ProtoName, _CmdMainId) ->
    ok;
build_proto_message([#build_proto_msg{name = Name,
    c2s = undefined, s2c = undefined} | _], _ProtoVersion, ProtoName, _CmdMainId) ->
    io:format("msg ~p_~p without c2s and s2c~n", [ProtoName, Name]),
    halt(1);
build_proto_message([#build_proto_msg{name = Name} | _T],
    _ProtoVersion, ProtoName, CmdMainId) when CmdMainId =< 0 ->
    io:format("msg ~p_~p without cmd_main_id", [ProtoName, Name]),
    halt(1);
build_proto_message(Data, ProtoVersion, ProtoName, CmdMainId) ->
    Maps = get_msg_main_maps(),
    case maps:is_key(CmdMainId, Maps) of
        true ->
            io:format("msg ~p cmd_main_id ~p is duplicated", [ProtoName, CmdMainId]),
            halt(1);
        _ ->
            set_msg_main_maps(Maps#{CmdMainId => true}),
            do_build_proto_message(Data, ProtoVersion, ProtoName, CmdMainId)
    end.

do_build_proto_message([], _ProtoVersion, _ProtoName, _CmdMainId) ->
    ok;
do_build_proto_message([Message | T], ProtoVersion, ProtoName, CmdMainId) ->
    #build_proto_msg{
        c2s = C2S,
        s2c = S2C
    } = Message,
    Device = get_proto_file_device(),
    build_cs_message(C2S, ProtoVersion, ProtoName, CmdMainId, Message, Device, c2s),
    build_cs_message(S2C, ProtoVersion, ProtoName, CmdMainId, Message, Device, s2c),
    do_build_proto_message(T, ProtoVersion, ProtoName, CmdMainId).

%% @doc 构建结构消息
build_proto_struct([], _ProtoVersion) ->
    ok;
build_proto_struct([#build_proto_struct{} = Struct | T], ProtoVersion) ->
    Device = get_proto_file_device(),
    build_struct_message(ProtoVersion, Struct, Device),
    build_proto_struct(T, ProtoVersion).

build_struct_message(ProtoVersion, Struct, Device) ->
    #build_proto_struct{
        fields = Fields,
        name = Name,
        desc = Desc
    } = Struct,
    io:format(Device, "~n// ~s~n",
        [unicode:characters_to_binary(Desc)]),
    io:format(Device, "message ~s {~n", [Name]),
    build_field_bodies(Fields, ProtoVersion, Device),
    io:format(Device, "}~n", []).

%% @doc 构建前后端交互消息
build_cs_message(undefined, _ProtoVersion, _ProtoName, _CmdMainId, _Message, _Device, _) ->
    ok;
build_cs_message(CSMsgList, ProtoVersion, ProtoName, CmdMainId, Message, Device, CSType) ->
    NameList = build_proto_cmd:get_by_msg_name_list(),
    CmdList = build_proto_cmd:get_by_cmd_list(),
    #build_proto_msg{
        cmd_sub_id = CmdSubId,
        name = Name,
        desc = Desc
    } = Message,
    CmdKey = {CSType, CmdMainId, CmdSubId},
    NameKey = list_to_atom(ProtoName ++ "_" ++ Name ++ "_" ++ atom_to_list(CSType)),
    PbMod = list_to_atom(ProtoName ++ "_pb"),
    HandlerName = list_to_atom(ProtoName ++ "_phandler"),
    NameList1 = [{NameKey, #{pb_mod => PbMod,
        cmd_main_id => CmdMainId, cmd_sub_id => CmdSubId, handler => HandlerName}} | NameList],
    case lists:keyfind(CmdKey, 1, CmdList) of
        false ->
            CmdList1 = [{CmdKey, #{pb_mod => PbMod, msg_name => NameKey}} | CmdList],
            build_proto_cmd:set_by_msg_name_list(NameList1),
            build_proto_cmd:set_by_cmd_list(CmdList1),
            io:format(Device, "~n// ~s~n// CMD_MAIN_ID:~p CMD_SUB_ID:~p~n",
                [unicode:characters_to_binary(Desc), CmdMainId, CmdSubId]),
            io:format(Device, "message ~s_~s_~p {~n", [ProtoName, Name, CSType]),
            build_field_bodies(CSMsgList, ProtoVersion, Device),
            io:format(Device, "}~n", []);
        _ ->
            io:format("proto ~p cmd_sub_id ~p duplicated", [ProtoName, CmdSubId]),
            halt(1)
    end.



build_field_bodies(Fields, ProtoVersion, Device) ->
    build_field_bodies(Fields, ProtoVersion, Device, 1).
build_field_bodies([], _ProtoVersion, _Device, _Tag) ->
    ok;
build_field_bodies(null, _ProtoVersion, _Device, _Tag) ->
    ok;
build_field_bodies([Field | T], ProtoVersion, Device, Tag) ->
    {FieldName, FieldTypeWithDesc} = Field,
    MP = get_msg_desc_mp(),
    {FieldType, FieldDesc} =
        case re:run(FieldTypeWithDesc, MP, [{capture, all_but_first, binary}]) of
            {match, [MatchOne, MatchTwo]} ->
                {MatchOne, MatchTwo};
            _ ->
                {FieldTypeWithDesc, ""}
        end,
    build_field_body(ProtoVersion, Device, FieldType, FieldName, FieldDesc, Tag),
    build_field_bodies(T, ProtoVersion, Device, Tag + 1).

build_field_body(?PROTO_VERSION_2, Device, FieldType, FieldName, FieldDesc, Tag) ->
    case FieldType of
        <<"repeated", _/binary>> ->
            io:format(Device, "\t~s ~s = ~p; // ~s ~n", [FieldType, FieldName, Tag, FieldDesc]);
        _ ->
            io:format(Device, "\toptional ~s ~s = ~p; // ~s~n", [FieldType, FieldName, Tag, FieldDesc])
    end;
build_field_body(?PROTO_VERSION_3, Device, FieldType, FieldName, FieldDesc, Tag) ->
    case FieldType of
        <<"repeated", _/binary>> ->
            io:format(Device, "\t~s ~s = ~p; // ~s ~n", [FieldType, FieldName, Tag, FieldDesc]);
        _ ->
            io:format(Device, "\t~s ~s = ~p; // ~s~n", [FieldType, FieldName, Tag, FieldDesc])
    end.
